iT邦幫忙

2022 iThome 鐵人賽

DAY 7
0
Software Development

30天學會Golang系列 第 7

Day07 - Go的閉包

  • 分享至 

  • xImage
  •  

閉包

閉包是一個蠻有趣的功能,顧名思義就是 func 裡面還有一個 func,我們先觀察一下以下程式碼

func adder() func(int) int {
    sum := 0
    return func(v int) int {
        sum += v
        return sum
    }
}

func main() {
    a := adder()
    for i := 0; i < 5; i++ {
        fmt.Printf("0 + 1 + ... + %d = %d \n", i, a(i))
    }
}

輸出結果為:

0 + 1 + ... + 0 = 0 
0 + 1 + ... + 1 = 1 
0 + 1 + ... + 2 = 3 
0 + 1 + ... + 3 = 6 
0 + 1 + ... + 4 = 10 

這個 func adder() func(int) int {} ,看起來有點複雜,我們先一步步拆解,根據 func 的概念,在 adder() 裡面的表示輸入參數,表示這個函數是沒有輸入參數的,那後面的 func(int) int,表示為 adder() 的輸出,所以就是 adder() 輸出一個 func,該 func 有一個輸入 int 且輸出一個 int,因此 adder() 最後回傳的也必須是 func(int) int,理解了整個架構後,我們就來看看這個 adder() 有什麼特別之處吧。
在 main 裡面的 a 先宣告為 adder() ,然後接下來就是不斷的輸出 a(i),奇怪的是如果依照 func 的概念,每次進入 func ,理論上裡面的變數應該都會是初始預設值,也就是 sum := 0,然後透過 sum += v,得到 sum,所以按照這個概念,a(1)應該要為1,a(2)應該是2,a(3)應該是3...,但很顯然,每次計算完的sum都被保留下來,保留到下次繼續累加,或者用更直接暴力的方式呈現

// func adder() func(int) int {
// 	sum := 0
// 	return func(v int) int {
// 		sum += v
// 		return sum
// 	}
// }

// func main() {
// 	a := adder()
	fmt.Println(a(5))
	fmt.Println(a(10))
// }

輸出結果為:

5
15

可以非常明顯的看到在 adder() func 中的 sum 會保留上一次的結果,那這就是閉包的功能,那我們可以用這個方式來理解一下到底中間發生了什麼事,當我們一開始創建 a := adder()時,對於 adder() 這個 func 來說,其實真正有被用到的是這個部分

func adder() func(int) int {
    sum := 0
//    return func(v int) int {
//        sum += v
//        return sum
//    }
}

func main() {
    a := adder()
}

那接下來當我們使用 a(5) 與 a(10) 時,其實真正有執行的是這個部分

//func adder() func(int) int {
//    sum := 0
    return func(v int) int {  // 真正在執行的是這個部分
        sum += v
        return sum
    }
//}

func main() {
//    a := adder()
    a(5)
    a(10)
}

因此 sum := 0 這行其實並不會被執行到,所以不會被重置,使得 sum 的值被保存了下來。那我們現在已經有了概念,那來看看這個例子,來猜測一下結果

// func adder() func(int) int {
// 	sum := 0
// 	return func(v int) int {
// 		sum += v
// 		return sum
// 	}
// }

// func main() {
	a := adder()
	fmt.Println(a(5))
	fmt.Println(a(10))
	a = adder()
	fmt.Println(a(5))
	fmt.Println(a(10))
// }

究竟這樣的結果會是 5, 15, 5, 15 還是 5, 15, 20, 35 呢?

5
15
5
15

沒錯,結果就是 5, 15, 5, 15 ,原因就是當 a = adder()執行時, sum := 0 再次被執行到,因此 sum 會被重置為 0

func adder() func(int) int {
    sum := 0
//    return func(v int) int {
//        sum += v
//        return sum
//    }
}

順便附註一下,adder()並不會額外賦予其他記憶體空間

// func adder() func(int) int {
// 	sum := 0
// 	return func(v int) int {
// 		sum += v
// 		return sum
// 	}
// }

// func main() {
	a := adder()
	fmt.Println(a(5), &a)
	fmt.Println(a(10), &a)
	a = adder()
	fmt.Println(a(5), &a)
	fmt.Println(a(10), &a)
// }

輸出結果就如你所預測一樣,a 的地址是不會改變的,因此可以確認確實是因為再次執行了adder() 中的 sum := 0,因而重置了 sum 的值,下面附結果證明

5 0x14000120018
15 0x14000120018
5 0x14000120018
15 0x14000120018

也可以換個寫法,結果也是一樣,至於哪種寫法比較好,就看個人喜好了,嘻嘻

type iAdder func(int) (int, iAdder)

func adder2(base int) iAdder {
	return func(v int) (int, iAdder) {
		return base + v, adder2(base + v)
	}
}

func main() {
	a2 := adder2(0)
	result, a2 := a2(5)
	fmt.Println(result)
	result, a2 = a2(10)
	fmt.Println(result)
}
第7天報到,閉包是什麼,感覺像是封閉的肉包,想起了台南有間好吃的包子店,克林肉包...(流口水

參考來源

  1. https://iter01.com/518537.html
  2. https://openhome.cc/Gossip/Go/Closure.html

代碼連結

https://github.com/luckyuho/ithome30-golang/tree/main/day07


上一篇
Day06 - Go的 interface (下)
下一篇
Day08 - Go的錯誤處理(error handling)
系列文
30天學會Golang31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言